package DriverInterface;

import java.io.*;
import java.net.*;
import java.util.ArrayList;

import javax.swing.JOptionPane;

import Control.LCPDControl;
import Control.LCPDConverterSettings;
import Control.LCPDData;

/**
 * This class communicate with the C-Driver Interface over ethernet and stores
 * the data. It runs as a single thread.
 * 
 * @author kurmt1@bfh.ch, woodr1@bfh.ch, eichs2@bfh.ch
 * @version 1.0
 */
public class LCPDDriverInterface implements Runnable {
	private final int CHANNEL_NUMBER = 2;
	private LCPDData SamplingData;
	private LCPDConverterSettings ConverterSettings;
	private float SleepTime;
	private ArrayList<Float>[] Samples;
	private int Counter = 0;
	private LCPDControl Control;

	private BufferedOutputStream outToServer;
	private BufferedInputStream inFromServer;
	private Socket clientSocket;
	private String ScopeIP;
	private int Port = 1234;
	private boolean DataAvilable = false;

	private byte SampleRatebuf[];
	private byte DataBuffer[];

	/**
	 * Start the thread open the network connection and communicate with the
	 * client (non-Javadoc)
	 * 
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		long Time;
		boolean TestFunction;
		String[] yesNoOptions = { "Yes", "No" }; 
		if(JOptionPane.showOptionDialog( null, 
		          "Would you work with simulated data?\n" +
		          "pleas only say no if you are sure that a driver interface server\n" +
		          "started!",               // question 
		          "Driver Interface",           // title 
		          JOptionPane.YES_NO_OPTION, 
		          JOptionPane.QUESTION_MESSAGE,  // icon 
		          null, yesNoOptions,yesNoOptions[0] )== JOptionPane.YES_OPTION)
		{
			TestFunction=true;
		}
		else TestFunction=false;
		try {
			openNetwork();
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		try {
			while (!Thread.currentThread().isInterrupted()) {
				if (TestFunction)
					TestFunction();
				// Time=System.currentTimeMillis();
				else
					ClientFunction();
				// System.out.println("Time: "+(System.currentTimeMillis()-Time));
			}
			System.out.println("Driver Interface: bye");
		} catch (Exception e) {

		} finally {
			try {
				closeNetwork();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	/**
	 * this is the client function, it try to request data and receive it.
	 */
	private void ClientFunction() {
		try {
			Thread.sleep(10);
		} catch (java.lang.InterruptedException t) {
			System.err.println(t);
		}
		// if we have to reinitialize the A/D Converter
		if (ConverterSettings.isRun() && ConverterSettings.isNewData()) {

			System.out.printf("Sending Settings + DataRequest");
			try {
				DataBuffer = new byte[ConverterSettings.getBufferSize() * 4];
				sendPacket((char) 1);
				while (!receivePacket())
					;
				ConverterSettings.setNewData(false);
			} catch (java.lang.Exception e) {
				System.err.println(e);
			}
		}
		// if we only would reinitialize the A/D-Converter (not implemented yet)
		else if (!ConverterSettings.isRun() && ConverterSettings.isNewData()) {
			System.out.printf("Sending Settings");
			DataBuffer = new byte[ConverterSettings.getBufferSize() * 4];
			try {
				sendPacket((char) 0);
			} catch (java.lang.Exception e) {
				System.err.println(e);
			}
			ConverterSettings.setNewData(false);
		}
		// if we only would catch new data
		else if (ConverterSettings.isRun() && !ConverterSettings.isNewData()) {
			try {
				outToServer.write((char) 67); // Make sure that this is the
												// first byte!!
				outToServer.write((char) 1); // we only want new data
				outToServer.flush();
				while (!receivePacket())
					; // Try to receive Data

			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	/**
	 * this is a test function to simulate some data in java. Actual only noise.
	 */
	private void TestFunction() {
		// on auto mode, smaller buffer can be used
		if (ConverterSettings.getTriggerMode() == LCPDConverterSettings.LCPDTriggerMode.Auto) {
			float Frequenz = 4 / ConverterSettings.getDisplayTime();
			SleepTime = (float) 1000000000 / ConverterSettings.getSampleRate()
					* ConverterSettings.getBufferSize() / 2;
			int SleepTimeMS = (int) SleepTime / 1000000;
			int SleepTimeNS = (int) SleepTime % 1000000;
			float Amplitude;
			long Time;
			double X;
			// Make noise in addiction with display time, sample rate, etc.
			for (int j = 0; j < ConverterSettings.getDisplayTime()
					* ConverterSettings.getSampleRate(); j += ConverterSettings
					.getBufferSize()) {
				// Time=System.nanoTime();
				for (int i = 0; i < ConverterSettings.getBufferSize(); i++) {

					// X=2*Math.PI*Frequenz*(j+i)/ConverterSettings.getSampleRate();
					// Sample.add((Float)(float)((Amplitude*Math.sin(X))+Math.random()/5));
					Samples[0].add((Float) (float) ((Math.random() - 0.5) * 2));
					Samples[1].add((Float) (float) ((Math.random() - 0.5) * 2));

				}
				// set data for each channel
				SamplingData.setSamples(0, Samples[0]);
				SamplingData.setSamples(1, Samples[1]);
				try {
					Thread.sleep(SleepTimeMS, SleepTimeNS);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// System.out.println("Size"+Sample.size()+" "+ConverterSettings.getBufferSize());
				// TestChart.repaint();
				// Time=System.nanoTime()-Time;
				// System.out.println("Thread:" + Time);
				Samples[0].clear();
				Samples[1].clear();
			}
		} else {
			// fill the whole buffer
			SleepTime = (float) 1000000000 / ConverterSettings.getSampleRate()
					* ConverterSettings.getBufferSize() / 2;
			int SleepTimeMS = (int) SleepTime / 1000000;
			int SleepTimeNS = (int) SleepTime % 1000000;
			for (int j = 0; j < ConverterSettings.getBufferSize(); j++) {
				Samples[0].add((Float) (float) ((Math.random() - 0.5) * 2));
				Samples[1].add((Float) (float) ((Math.random() - 0.5) * 2));
			}
			SamplingData.setSamples(0, Samples[0]);
			SamplingData.setSamples(1, Samples[1]);
			try {
				Thread.sleep(SleepTimeMS, SleepTimeNS);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
			}
			Samples[0].clear();
			Samples[1].clear();
		}
	}

	/**
	 * Constructer set the interface to comunicate with other threads and
	 * classes.
	 * 
	 * @param LCPDData pData Data interface
	 * 
	 * @param LCPDConverterSettings pConverterSettings the converter settings
	 * 
	 * @param LCPDControl pControl control interface to close the programm
	 */
	public LCPDDriverInterface(LCPDData pData,
			LCPDConverterSettings pConverterSettings, LCPDControl pControl) {
		SamplingData = pData;
		ConverterSettings = pConverterSettings;
		Samples = new ArrayList[CHANNEL_NUMBER];
		Samples[0] = new ArrayList<Float>();
		Samples[1] = new ArrayList<Float>();
		Control = pControl;
		SampleRatebuf = new byte[4];
	}

	/**
	 * Convert float to a byte array for communication
	 * 
	 * @param float f Float value to convert
	 * 
	 * @return short[] Array with for integer values
	 */
	private static short[] floatToByteArray(float f) {
		int i = Float.floatToRawIntBits(f);
		return intToByteArray(i);
	}

	/**
	 * Convert a integer to a byte array
	 * 
	 * @param int param value to convert
	 * 
	 * @return short[] a byte array of size 4
	 */
	private static short[] intToByteArray(int param) {
		short[] result = new short[4];
		result[3] = (short) ((param >> 24) & 0xFF);
		result[2] = (short) ((param >> 16) & 0xFF);
		result[1] = (short) ((param >> 8) & 0xFF);
		result[0] = (short) ((param) & 0xFF);
		return result;
	}

	/**
	 * Convert a Byte Array to a integer value
	 * 
	 * @param byte[] Buffer Array of size 4
	 * 
	 * @return int Integer Value
	 */
	private int ByteArrayToInt(byte[] Buffer) {
		int result = 0;
		result = ((((short) Buffer[3]) & 0xFF) << 24)
				| (((short) Buffer[2]) & 0xFF) << 16
				| (((short) Buffer[1]) & 0xFF) << 8
				| (((short) Buffer[0]) & 0xFF);
		return result;
	}

	/**
	 * send Buffer to the server
	 * 
	 * @param short[] pBuff Buffer to send
	 */
	private void writeOutBuff(short[] pBuff) throws Exception {
		for (int i = 0; i < pBuff.length; i++) {
			outToServer.write(pBuff[i]);
		}

	}

	/**
	 * send a Packet to the server
	 * @param char withDataRequest if we also will receive data
	 */
	private void sendPacket(char withDataRequest) throws Exception {

		// int i = Float.floatToRawIntBits(f);
		outToServer.write((char) 67); // Make sure that this is the first byte!!
		// max Voltage CHA and Coupling Mode CHA the CHB, etc.
		for (int i = 0; i < CHANNEL_NUMBER; i++) {
			writeOutBuff(floatToByteArray(ConverterSettings.getMaxVoltage(i)));
			outToServer.write((char) ConverterSettings.getCouplingMode(i)
					.ordinal());
		}
		writeOutBuff(floatToByteArray(ConverterSettings.getTotalTime()));

		writeOutBuff(floatToByteArray(ConverterSettings.getDisplayTime()));
		writeOutBuff(floatToByteArray(ConverterSettings.getBufferSize()));
		writeOutBuff(floatToByteArray(ConverterSettings.getTriggerLevel()));
		outToServer.write((char) ConverterSettings.getTriggerMode().ordinal());
		outToServer.write((char) ConverterSettings.getTriggerEdge().ordinal());
		outToServer.write((char) withDataRequest);
		outToServer.flush();
	}

	/**
	 * Add bytes from receive buffer to the sample array
	 * 
	 * @param int Channel which channel
	 * @param byte[] pBuffer 
	 */
	private void BufferToSample(int Channel, byte[] pBuffer) {
		for (int i = 0; i < pBuffer.length; i += 4) {
			Samples[Channel]
					.add(Float
							.intBitsToFloat((int) (((((short) pBuffer[i + 3]) & 0xFF) << 24)
									| (((short) pBuffer[i + 2]) & 0xFF) << 16
									| (((short) pBuffer[i + 1]) & 0xFF) << 8 | (((short) pBuffer[i]) & 0xFF))));
		}
	}
	/**
	 * receive data from the server
	 * 
	 * @return boolean true if we get data, false if not
	 */
	private boolean receivePacket() throws Exception {
		int n = 0;
		// n = inFromServer.read(receivebuf);
		if (inFromServer.available() >= 8 * ConverterSettings.getBufferSize() + 4) {
			inFromServer.read(SampleRatebuf);
			DataAvilable = true;
			ConverterSettings.setSampleRate(Float
					.intBitsToFloat(ByteArrayToInt(SampleRatebuf)));
			inFromServer.read(DataBuffer);
			BufferToSample(0, DataBuffer);
			inFromServer.read(DataBuffer);
			BufferToSample(1, DataBuffer);
			for (int i = 0; i < CHANNEL_NUMBER; i++) {
				SamplingData.setSamples(i, Samples[i]);
				Samples[i].clear();
			}

			return true;
		}
		return false;
	}

	/**
	 * open the network interface
	 * 
	 */
	public void openNetwork() throws Exception {

		clientSocket = new Socket(ConverterSettings.getIP(), 1234);
		inFromServer = new BufferedInputStream(clientSocket.getInputStream());
		outToServer = new BufferedOutputStream(clientSocket.getOutputStream());

	}
	/**
	 * close the netowrk interface
	 */
	public void closeNetwork() throws Exception {
		/* First send to close @ server */
		outToServer.close();
		inFromServer.close();
		clientSocket.close();

	}

}
